// 版权所有2009 Go作者。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// HTTP文件系统请求处理程序

package http

import (
	"errors"
	"fmt"
	"io"
	"io/fs"
	"mime"
	"mime/multipart"
	"net/textproto"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"time"
)

// Dir使用仅限于特定目录树的本机文件系统实现文件系统。
// 
// 虽然FileSystem.Open方法采用“/”分隔的路径，但Dir的字符串
// 值是本机文件系统上的文件名，而不是URL，因此它由filepath.Separator分隔，而filepath.Separator不一定是“/”。
// 
// 请注意，Dir可能会暴露敏感文件和目录。Dir将跟随指向目录树外的符号链接，如果从用户能够创建任意符号链接的目录中服务，这可能会特别危险。
// Dir还允许访问以句点开始的文件和目录，
// ，这可能会暴露敏感目录，如.git或敏感文件，如
// .htpasswd。要排除带前导句点的文件，请从服务器中删除文件/目录
// 或创建自定义文件系统实现。
// 
// 空目录被视为“”。
type Dir string

// mapDirOpenError将提供的非零错误从打开名
// 映射到可能更好的非零错误。特别是，它将有关将非目录中的文件打开到fs.ErrNotExist的操作系统特定错误
// 。见第18984期。
func mapDirOpenError(originalErr error, name string) error {
	if errors.Is(originalErr, fs.ErrNotExist) || errors.Is(originalErr, fs.ErrPermission) {
		return originalErr
	}

	parts := strings.Split(name, string(filepath.Separator))
	for i := range parts {
		if parts[i] == "" {
			continue
		}
		fi, err := os.Stat(strings.Join(parts[:i+1], string(filepath.Separator)))
		if err != nil {
			return originalErr
		}
		if !fi.IsDir() {
			return fs.ErrNotExist
		}
	}
	return originalErr
}

// Open使用操作系统实现文件系统。Open，打开文件以读取根目录
// 和相对于目录d的文件。
func (d Dir) Open(name string) (File, error) {
	if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) {
		return nil, errors.New("http: invalid character in file path")
	}
	dir := string(d)
	if dir == "" {
		dir = "."
	}
	fullName := filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name)))
	f, err := os.Open(fullName)
	if err != nil {
		return nil, mapDirOpenError(err, fullName)
	}
	return f, nil
}

// 文件系统实现对命名文件集合的访问。
// 文件路径中的元素以斜杠（'/'，U+002F）分隔
// 字符，与主机操作系统约定无关。
// 请参阅FileServer函数以将文件系统转换为处理程序。
// 
// 此接口早于fs.fs接口，可以使用它：
// fs适配器函数将fs.fs转换为文件系统。
type FileSystem interface {
	Open(name string) (File, error)
}

// 文件系统的Open方法返回一个文件，并且可以由文件服务器实现提供
// 服务。
// 
// 这些方法的行为应该与*os.File上的方法相同。
type File interface {
	io.Closer
	io.Reader
	io.Seeker
	Readdir(count int) ([]fs.FileInfo, error)
	Stat() (fs.FileInfo, error)
}

type anyDirs interface {
	len() int
	name(i int) string
	isDir(i int) bool
}

type fileInfoDirs []fs.FileInfo

func (d fileInfoDirs) len() int          { return len(d) }
func (d fileInfoDirs) isDir(i int) bool  { return d[i].IsDir() }
func (d fileInfoDirs) name(i int) string { return d[i].Name() }

type dirEntryDirs []fs.DirEntry

func (d dirEntryDirs) len() int          { return len(d) }
func (d dirEntryDirs) isDir(i int) bool  { return d[i].IsDir() }
func (d dirEntryDirs) name(i int) string { return d[i].Name() }

func dirList(w ResponseWriter, r *Request, f File) {
	// 更喜欢使用ReadDir而不是ReadDir，因为前者不需要在Unix上对目录的每个条目调用
	// Stat。
	var dirs anyDirs
	var err error
	if d, ok := f.(fs.ReadDirFile); ok {
		var list dirEntryDirs
		list, err = d.ReadDir(-1)
		dirs = list
	} else {
		var list fileInfoDirs
		list, err = f.Readdir(-1)
		dirs = list
	}

	if err != nil {
		logf(r, "http: error reading directory: %v", err)
		Error(w, "Error reading directory", StatusInternalServerError)
		return
	}
	sort.Slice(dirs, func(i, j int) bool { return dirs.name(i) < dirs.name(j) })

	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	fmt.Fprintf(w, "<pre>\n")
	for i, n := 0, dirs.len(); i < n; i++ {
		name := dirs.name(i)
		if dirs.isDir(i) {
			name += "/"
		}
		// 名称可能包含“？”或“#”，必须对其进行转义才能保持
		// URL路径的一部分，并且不指示查询的开始
		// 字符串或片段。
		url := url.URL{Path: name}
		fmt.Fprintf(w, "<a href=\"%s\">%s</a>\n", url.String(), htmlReplacer.Replace(name))
	}
	fmt.Fprintf(w, "</pre>\n")
}

// 使用
// 提供的ReadSeek中的内容对请求进行ServeContent回复。ServeContent over io.Copy 
// 的主要优点是它可以正确处理范围请求，设置MIME类型，并且
// 可以处理是否匹配、是否未修改自、是否不匹配、是否修改自、
// 以及范围请求。
// 
// 如果未设置响应的内容类型头，ServeContent 
// 首先尝试从名称的文件扩展名推断类型，如果失败，则返回到读取内容
// 的第一个块并将其传递给DetectContentType。
// 名称未使用；特别是它可以为空，并且在响应中为
// 从未发送。
// 
// 如果modtime不是零时间或Unix纪元，则ServeContent 
// 将其包含在响应中最后修改的头中。如果
// 请求包含一个If-Modified-Since头，则ServeContent使用
// modtime来决定是否需要发送内容。
// 
// 内容的Seek方法必须有效：ServeContent使用
// 对内容末尾进行Seek以确定其大小。
// 
// 如果调用方设置了w的ETag头，格式符合RFC 7232第2.3节的要求，
// ServeContent使用它来处理使用If-Match、If-None-Match或If-Range的请求。
// 
// 请注意，*os.File实现io.ReadSeeker接口。
func ServeContent(w ResponseWriter, req *Request, name string, modtime time.Time, content io.ReadSeeker) {
	sizeFunc := func() (int64, error) {
		size, err := content.Seek(0, io.SeekEnd)
		if err != nil {
			return 0, errSeeker
		}
		_, err = content.Seek(0, io.SeekStart)
		if err != nil {
			return 0, errSeeker
		}
		return size, nil
	}
	serveContent(w, req, name, modtime, sizeFunc, content)
}

// 当内容
// 未正确查找时，ServeContent的sizeFunc将返回errSeeker。sizeFunc回复中未包含基本搜索者的错误文本
// 因此不会通过HTTP发送给最终
// 用户。
var errSeeker = errors.New("seeker can't seek")

// 如果
// 所有字节范围规范值都大于内容大小。
var errNoOverlap = errors.New("invalid range: failed to overlap")

// 如果名称为空，则文件名未知。（用于mime类型，嗅探前）
// 如果modtime.IsZero（），则modtime未知。
// 必须查找文件开头的内容。
// 最多调用一次sizeFunc。它的错误（如果有）将在HTTP响应中发送。
func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time, sizeFunc func() (int64, error), content io.ReadSeeker) {
	setLastModified(w, modtime)
	done, rangeReq := checkPreconditions(w, r, modtime)
	if done {
		return
	}

	code := StatusOK

	// 如果未设置内容类型，请使用文件扩展名查找它，但
	// 如果内容类型显式未设置，请不要嗅探该类型。
	ctypes, haveType := w.Header()["Content-Type"]
	var ctype string
	if !haveType {
		ctype = mime.TypeByExtension(filepath.Ext(name))
		if ctype == "" {
			// 读取一个块以决定utf-8文本和二进制
			var buf [sniffLen]byte
			n, _ := io.ReadFull(content, buf[:])
			ctype = DetectContentType(buf[:n])
			_, err := content.Seek(0, io.SeekStart) // 倒带以输出整个文件
			if err != nil {
				Error(w, "seeker can't seek", StatusInternalServerError)
				return
			}
		}
		w.Header().Set("Content-Type", ctype)
	} else if len(ctypes) > 0 {
		ctype = ctypes[0]
	}

	size, err := sizeFunc()
	if err != nil {
		Error(w, err.Error(), StatusInternalServerError)
		return
	}

	// 句柄内容范围标头。
	sendSize := size
	var sendContent io.Reader = content
	if size >= 0 {
		ranges, err := parseRange(rangeReq, size)
		if err != nil {
			if err == errNoOverlap {
				w.Header().Set("Content-Range", fmt.Sprintf("bytes */%d", size))
			}
			Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)
			return
		}
		if sumRangesSize(ranges) > size {
			// 所有范围内的字节总数
			// 大于
			// 本身的文件大小，因此这可能是一次攻击，或
			// 哑客户端。忽略范围请求。
			ranges = nil
		}
		switch {
		case len(ranges) == 1:
			// RFC 7233，第4.1节：
			// “如果正在传输单个部分，则生成206响应的服务器
			// 必须生成一个
			// 内容范围标头字段，该字段描述所选表示的范围
			// 以及包含该范围的
			// 有效负载。
			// /。。。
			// 服务器不能对单个范围的请求生成多部分响应，因为
			// 不请求多部分的客户端可能不支持
			// 多部分响应。“
			ra := ranges[0]
			if _, err := content.Seek(ra.start, io.SeekStart); err != nil {
				Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)
				return
			}
			sendSize = ra.length
			code = StatusPartialContent
			w.Header().Set("Content-Range", ra.contentRange(size))
		case len(ranges) > 1:
			sendSize = rangesMIMESize(ranges, ctype, size)
			code = StatusPartialContent

			pr, pw := io.Pipe()
			mw := multipart.NewWriter(pw)
			w.Header().Set("Content-Type", "multipart/byteranges; boundary="+mw.Boundary())
			sendContent = pr
			defer pr.Close() // 导致写入goroutine失败，如果CopyN未完成，则退出。
			go func() {
				for _, ra := range ranges {
					part, err := mw.CreatePart(ra.mimeHeader(ctype, size))
					if err != nil {
						pw.CloseWithError(err)
						return
					}
					if _, err := content.Seek(ra.start, io.SeekStart); err != nil {
						pw.CloseWithError(err)
						return
					}
					if _, err := io.CopyN(part, content, ra.length); err != nil {
						pw.CloseWithError(err)
						return
					}
				}
				mw.Close()
				pw.Close()
			}()
		}

		w.Header().Set("Accept-Ranges", "bytes")
		if w.Header().Get("Content-Encoding") == "" {
			w.Header().Set("Content-Length", strconv.FormatInt(sendSize, 10))
		}
	}

	w.WriteHeader(code)

	if r.Method != "HEAD" {
		io.CopyN(w, sendContent, sendSize)
	}
}

// scanETag确定s处是否存在语法有效的ETag。如果是，则返回
// ETag和使用ETag后的剩余文本。否则，
// 返回“”。
func scanETag(s string) (etag string, remain string) {
	s = textproto.TrimString(s)
	start := 0
	if strings.HasPrefix(s, "W/") {
		start = 2
	}
	if len(s[start:]) < 2 || s[start] != '"' {
		return "", ""
	}
	// ETag是W/“text”或“text”。
	// 参见RFC 7232 2.3。
	for i := start + 1; i < len(s); i++ {
		c := s[i]
		switch {
		// ETag中允许的字符值。
		case c == 0x21 || c >= 0x23 && c <= 0x7E || c >= 0x80:
		case c == '"':
			return s[:i+1], s[i+1:]
		default:
			return "", ""
		}
	}
	return "", ""
}

// etagStrongMatch使用强ETag比较报告a和b是否匹配。
// 假设a和b是有效的ETag。
func etagStrongMatch(a, b string) bool {
	return a == b && a != "" && a[0] == '"'
}

// etagWeakMatch使用弱ETag比较报告a和b是否匹配。
// 假设a和b是有效的ETag。
func etagWeakMatch(a, b string) bool {
	return strings.TrimPrefix(a, "W/") == strings.TrimPrefix(b, "W/")
}

// condResult是HTTP请求先决条件检查的结果。
// 参见https:
type condResult int

const (
	condNone condResult = iota
	condTrue
	condFalse
)

func checkIfMatch(w ResponseWriter, r *Request) condResult {
	im := r.Header.Get("If-Match")
	if im == "" {
		return condNone
	}
	for {
		im = textproto.TrimString(im)
		if len(im) == 0 {
			break
		}
		if im[0] == ',' {
			im = im[1:]
			continue
		}
		if im[0] == '*' {
			return condTrue
		}
		etag, remain := scanETag(im)
		if etag == "" {
			break
		}
		if etagStrongMatch(etag, w.Header().get("Etag")) {
			return condTrue
		}
		im = remain
	}

	return condFalse
}

func checkIfUnmodifiedSince(r *Request, modtime time.Time) condResult {
	ius := r.Header.Get("If-Unmodified-Since")
	if ius == "" || isZeroTime(modtime) {
		return condNone
	}
	t, err := ParseTime(ius)
	if err != nil {
		return condNone
	}

	// 上次修改的标头会截断亚秒精度，因此
	// modtime也需要截断。
	modtime = modtime.Truncate(time.Second)
	if modtime.Before(t) || modtime.Equal(t) {
		return condTrue
	}
	return condFalse
}

func checkIfNoneMatch(w ResponseWriter, r *Request) condResult {
	inm := r.Header.get("If-None-Match")
	if inm == "" {
		return condNone
	}
	buf := inm
	for {
		buf = textproto.TrimString(buf)
		if len(buf) == 0 {
			break
		}
		if buf[0] == ',' {
			buf = buf[1:]
			continue
		}
		if buf[0] == '*' {
			return condFalse
		}
		etag, remain := scanETag(buf)
		if etag == "" {
			break
		}
		if etagWeakMatch(etag, w.Header().get("Etag")) {
			return condFalse
		}
		buf = remain
	}
	return condTrue
}

func checkIfModifiedSince(r *Request, modtime time.Time) condResult {
	if r.Method != "GET" && r.Method != "HEAD" {
		return condNone
	}
	ims := r.Header.Get("If-Modified-Since")
	if ims == "" || isZeroTime(modtime) {
		return condNone
	}
	t, err := ParseTime(ims)
	if err != nil {
		return condNone
	}
	// 上次修改的标头会截断亚秒精度，因此
	// modtime也需要截断。
	modtime = modtime.Truncate(time.Second)
	if modtime.Before(t) || modtime.Equal(t) {
		return condFalse
	}
	return condTrue
}

func checkIfRange(w ResponseWriter, r *Request, modtime time.Time) condResult {
	if r.Method != "GET" && r.Method != "HEAD" {
		return condNone
	}
	ir := r.Header.get("If-Range")
	if ir == "" {
		return condNone
	}
	etag, _ := scanETag(ir)
	if etag != "" {
		if etagStrongMatch(etag, w.Header().Get("Etag")) {
			return condTrue
		} else {
			return condFalse
		}
	}
	// If范围值通常是ETag值，但也可能是
	// modtime日期。见golang.org/issue/8367。
	if modtime.IsZero() {
		return condFalse
	}
	t, err := ParseTime(ir)
	if err != nil {
		return condFalse
	}
	if t.Unix() == modtime.Unix() {
		return condTrue
	}
	return condFalse
}

var unixEpochTime = time.Unix(0, 0)

// isZeroTime报告t是否明显未指定（零或Unix（）=0）。
func isZeroTime(t time.Time) bool {
	return t.IsZero() || t.Equal(unixEpochTime)
}

func setLastModified(w ResponseWriter, modtime time.Time) {
	if !isZeroTime(modtime) {
		w.Header().Set("Last-Modified", modtime.UTC().Format(TimeFormat))
	}
}

func writeNotModified(w ResponseWriter) {
	// RFC 7232第4.1节：
	// 发送方不应生成除上述字段之外的表示元数据，除非上述元数据用于指导缓存更新（例如，如果
	// 响应没有ETag字段，则上次修改可能有用）.
	h := w.Header()
	delete(h, "Content-Type")
	delete(h, "Content-Length")
	if h.Get("Etag") != "" {
		delete(h, "Last-Modified")
	}
	w.WriteHeader(StatusNotModified)
}

// CheckPremissions评估请求的先决条件，并报告先决条件是否导致发送StatusNotModified或StatusPremissionFailed.
func checkPreconditions(w ResponseWriter, r *Request, modtime time.Time) (done bool, rangeHeader string) {
	// 此函数仔细遵循RFC 7232第6节。
	ch := checkIfMatch(w, r)
	if ch == condNone {
		ch = checkIfUnmodifiedSince(r, modtime)
	}
	if ch == condFalse {
		w.WriteHeader(StatusPreconditionFailed)
		return true, ""
	}
	switch checkIfNoneMatch(w, r) {
	case condFalse:
		if r.Method == "GET" || r.Method == "HEAD" {
			writeNotModified(w)
			return true, ""
		} else {
			w.WriteHeader(StatusPreconditionFailed)
			return true, ""
		}
	case condNone:
		if checkIfModifiedSince(r, modtime) == condFalse {
			writeNotModified(w)
			return true, ""
		}
	}

	rangeHeader = r.Header.get("Range")
	if rangeHeader != "" && checkIfRange(w, r, modtime) == condFalse {
		rangeHeader = ""
	}
	return false, rangeHeader
}

// 名称以“/”分隔，而不是filepath.Separator.
func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirect bool) {
	const indexPage = "/index.html"

	// 将…/index.html重定向到…/
	// 无法使用重定向（）因为这会使路径成为绝对路径，所以在StripPrefix下运行时会出现问题。如果目录名不是以斜杠结尾，则重定向到规范路径：/在目录url的末尾
	if strings.HasSuffix(r.URL.Path, indexPage) {
		localRedirect(w, r, "./")
		return
	}

	f, err := fs.Open(name)
	if err != nil {
		msg, code := toHTTPError(err)
		Error(w, msg, code)
		return
	}
	defer f.Close()

	d, err := f.Stat()
	if err != nil {
		msg, code := toHTTPError(err)
		Error(w, msg, code)
		return
	}

	if redirect {
		// r.url.path始终以/
		url := r.URL.Path
		if d.IsDir() {
			if url[len(url)-1] != '/' {
				localRedirect(w, r, path.Base(url)+"/")
				return
			}
		} else {
			if url[len(url)-1] == '/' {
				localRedirect(w, r, "../"+path.Base(url))
				return
			}
		}
	}

	if d.IsDir() {
		url := r.URL.Path
		// 重定向
		if url == "" || url[len(url)-1] != '/' {
			localRedirect(w, r, path.Base(url)+"/")
			return
		}

		// 使用目录index.html的内容，如果存在
		index := strings.TrimSuffix(name, "/") + indexPage
		ff, err := fs.Open(index)
		if err == nil {
			defer ff.Close()
			dd, err := ff.Stat()
			if err == nil {
				name = index
				d = dd
				f = ff
			}
		}
	}

	// 仍然是一个目录？（我们没有找到index.html文件）
	if d.IsDir() {
		if checkIfModifiedSince(r, d.ModTime()) == condFalse {
			writeNotModified(w)
			return
		}
		setLastModified(w, d.ModTime())
		dirList(w, r, f)
		return
	}

	// serveContent将检查修改时间
	sizeFunc := func() (int64, error) { return d.Size(), nil }
	serveContent(w, r, d.Name(), d.ModTime(), sizeFunc, f)
}

// toHTTPError为给定的非零错误值返回非特定的HTTP错误消息和状态代码
// 。重要的是，toHTTPError不会实际返回error.error（），因为msg和httpStatus返回给用户，所以
// 和历史上Go的ServeContent对于
// 所有错误都只返回“404未找到”。我们不希望开始在错误消息中泄漏信息。
func toHTTPError(err error) (msg string, httpStatus int) {
	if errors.Is(err, fs.ErrNotExist) {
		return "404 page not found", StatusNotFound
	}
	if errors.Is(err, fs.ErrPermission) {
		return "403 Forbidden", StatusForbidden
	}
	// 默认值：
	return "500 Internal Server Error", StatusInternalServerError
}

// localRedirect给出一个永久移动的响应。
// 它不会像重定向那样将相对路径转换为绝对路径。
func localRedirect(w ResponseWriter, r *Request, newPath string) {
	if q := r.URL.RawQuery; q != "" {
		newPath += "?" + q
	}
	w.Header().Set("Location", newPath)
	w.WriteHeader(StatusMovedPermanently)
}

// ServeFile使用命名的
// 文件或目录的内容答复请求。
// 
// 如果提供的文件或目录名是相对路径，则它是相对于当前目录的
// 解释的，并且可能上升到
// 父目录。如果提供的名称是由用户
// 输入构造的，则应在调用ServeFile之前对其进行清理。
// 
// 作为预防措施，ServeFile将拒绝r.URL.Path 
// 包含“.”Path元素的请求；这可以防止调用方使用
// 可能会不安全地使用filepath。在不清除
// 的情况下，在r.URL.Path上加入，然后使用该filepath.Join result作为name参数。
// 
// 作为另一种特殊情况，ServeFile将r.URL.Path 
// 以“/index.html”结尾的任何请求重定向到同一路径，而不使用最终的
// /“index.html”。要避免此类重定向，请修改路径或
// 使用ServeContent。
// 
// 除这两种特殊情况外，ServeFile不使用
// r.URL.Path来选择要服务的文件或目录；仅使用名称参数中提供的
// 文件或目录。
func ServeFile(w ResponseWriter, r *Request, name string) {
	if containsDotDot(r.URL.Path) {
		// 太多程序使用r.URL.Path来构造
		// serveFile的参数。拒绝请求，前提是此处发生
		// 并且可能不需要“.”。
		// 请注意，名称可能不包含“.”，例如，如果代码（仍然是
		// 错误地）使用了filepath.Join（myDir，r.URL.Path）。
		Error(w, "invalid URL path", StatusBadRequest)
		return
	}
	dir, file := filepath.Split(name)
	serveFile(w, r, Dir(dir), file, false)
}

func containsDotDot(v string) bool {
	if !strings.Contains(v, "..") {
		return false
	}
	for _, ent := range strings.FieldsFunc(v, isSlashRune) {
		if ent == ".." {
			return true
		}
	}
	return false
}

func isSlashRune(r rune) bool { return r == '/' || r == '\\' }

type fileHandler struct {
	root FileSystem
}

type ioFS struct {
	fsys fs.FS
}

type ioFile struct {
	file fs.File
}

func (f ioFS) Open(name string) (File, error) {
	if name == "/" {
		name = "."
	} else {
		name = strings.TrimPrefix(name, "/")
	}
	file, err := f.fsys.Open(name)
	if err != nil {
		return nil, err
	}
	return ioFile{file}, nil
}

func (f ioFile) Close() error               { return f.file.Close() }
func (f ioFile) Read(b []byte) (int, error) { return f.file.Read(b) }
func (f ioFile) Stat() (fs.FileInfo, error) { return f.file.Stat() }

var errMissingSeek = errors.New("io.File missing Seek method")
var errMissingReadDir = errors.New("io.File directory missing ReadDir method")

func (f ioFile) Seek(offset int64, whence int) (int64, error) {
	s, ok := f.file.(io.Seeker)
	if !ok {
		return 0, errMissingSeek
	}
	return s.Seek(offset, whence)
}

func (f ioFile) ReadDir(count int) ([]fs.DirEntry, error) {
	d, ok := f.file.(fs.ReadDirFile)
	if !ok {
		return nil, errMissingReadDir
	}
	return d.ReadDir(count)
}

func (f ioFile) Readdir(count int) ([]fs.FileInfo, error) {
	d, ok := f.file.(fs.ReadDirFile)
	if !ok {
		return nil, errMissingReadDir
	}
	var list []fs.FileInfo
	for {
		dirs, err := d.ReadDir(count - len(list))
		for _, dir := range dirs {
			info, err := dir.Info()
			if err != nil {
				// 假装它不存在，比如（*os.File）。Readdir确实存在。
				continue
			}
			list = append(list, info)
		}
		if err != nil {
			return list, err
		}
		if count < 0 || len(list) >= count {
			break
		}
	}
	return list, nil
}

// FS将fsys转换为文件系统实现，
// 用于文件服务器和NewFileTransport。
func FS(fsys fs.FS) FileSystem {
	return ioFS{fsys}
}

// 文件服务器返回一个处理程序，该处理程序为HTTP请求提供服务
// 文件系统的内容以根为根。
// 
// 作为一种特殊情况，返回的文件服务器会将任何请求
// 以“/index.html”结尾的重定向到同一路径，而不使用最终的
// “index.html”。要使用操作系统的文件系统实现，请使用http.Dir:
// 
// http.Handle（“/”，http.FileServer（http.Dir（“/tmp”））
// 
// ，使用http.FS进行转换：
// 
// http.Handle（“/”，http.FileServer（http.FS（fsys）））
// 
func FileServer(root FileSystem) Handler {
	return &fileHandler{root}
}

func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) {
	upath := r.URL.Path
	if !strings.HasPrefix(upath, "/") {
		upath = "/" + upath
		r.URL.Path = upath
	}
	serveFile(w, r, f.root, path.Clean(upath), true)
}

// httpRange指定要发送到客户端的字节范围。
type httpRange struct {
	start, length int64
}

func (r httpRange) contentRange(size int64) string {
	return fmt.Sprintf("bytes %d-%d/%d", r.start, r.start+r.length-1, size)
}

func (r httpRange) mimeHeader(contentType string, size int64) textproto.MIMEHeader {
	return textproto.MIMEHeader{
		"Content-Range": {r.contentRange(size)},
		"Content-Type":  {contentType},
	}
}

// parseRange根据RFC 7233解析范围标头字符串。
// 如果没有任何范围重叠，则返回errNoOverlap。
func parseRange(s string, size int64) ([]httpRange, error) {
	if s == "" {
		return nil, nil // header不存在
	}
	const b = "bytes="
	if !strings.HasPrefix(s, b) {
		return nil, errors.New("invalid range")
	}
	var ranges []httpRange
	noOverlap := false
	for _, ra := range strings.Split(s[len(b):], ",") {
		ra = textproto.TrimString(ra)
		if ra == "" {
			continue
		}
		i := strings.Index(ra, "-")
		if i < 0 {
			return nil, errors.New("invalid range")
		}
		start, end := textproto.TrimString(ra[:i]), textproto.TrimString(ra[i+1:])
		var r httpRange
		if start == "" {
			// 如果未指定start，end指定相对于文件结尾的范围start，
			// 我们正在处理<suffix length>
			// 根据
			// RFC 7233第2.1节，它必须是一个非负整数“字节范围“.
			if end == "" || end[0] == '-' {
				return nil, errors.New("invalid range")
			}
			i, err := strconv.ParseInt(end, 10, 64)
			if i < 0 || err != nil {
				return nil, errors.New("invalid range")
			}
			if i > size {
				i = size
			}
			r.start = size - i
			r.length = size - r.start
		} else {
			i, err := strconv.ParseInt(start, 10, 64)
			if err != nil || i < 0 {
				return nil, errors.New("invalid range")
			}
			if i >= size {
				// 如果范围开始于内容大小之后，则它不会重叠。
				noOverlap = true
				continue
			}
			r.start = i
			if end == "" {
				// 若未指定结束，则范围将扩展到文件的结尾。
				r.length = size - r.start
			} else {
				i, err := strconv.ParseInt(end, 10, 64)
				if err != nil || r.start > i {
					return nil, errors.New("invalid range")
				}
				if i >= size {
					i = size - 1
				}
				r.length = i - r.start + 1
			}
		}
		ranges = append(ranges, r)
	}
	if noOverlap && len(ranges) == 0 {
		// 指定的范围与内容不重叠。
		return nil, errNoOverlap
	}
	return ranges, nil
}

// countingWriter计算写入的字节数。
type countingWriter int64

func (w *countingWriter) Write(p []byte) (n int, err error) {
	*w += countingWriter(len(p))
	return len(p), nil
}

// RangeSimesize返回将提供的范围编码为多部分响应所需的字节数。
func rangesMIMESize(ranges []httpRange, contentType string, contentSize int64) (encSize int64) {
	var w countingWriter
	mw := multipart.NewWriter(&w)
	for _, ra := range ranges {
		mw.CreatePart(ra.mimeHeader(contentType, contentSize))
		encSize += ra.length
	}
	mw.Close()
	encSize += int64(w)
	return
}

func sumRangesSize(ranges []httpRange) (size int64) {
	for _, ra := range ranges {
		size += ra.length
	}
	return
}
